/*
 * Copyright 1999-2002 Carnegie Mellon University.  
 * Portions Copyright 2002 Sun Microsystems, Inc.  
 * Portions Copyright 2002 Mitsubishi Electric Research Laboratories.
 * All Rights Reserved.  Use is subject to license terms.
 * 
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL 
 * WARRANTIES.
 *
 */
package edu.cmu.sphinx.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;

/**
 * A client for a socketed command interpreter. Hooks up to a socketed command interpreter and allows the sending of
 * commands.
 *
 * @see CommandInterpreter
 */

public class SocketCommandClient {

    private String host;
    private int port;
    private Socket socket;
    private BufferedReader inReader;
    private PrintWriter outWriter;


    /**
     * Creates a socket command client at the given host and port.
     *
     * @param host the host machine
     * @param port the port to use
     * @throws IOException if setup went wrong
     */
    public SocketCommandClient(String host, int port)
            throws IOException {
        this.host = host;
        this.port = port;
        open();
    }


    /** Creats a SocketCommandClient with no connection open must be called. */
    public SocketCommandClient() {
    }


    /**
     * Opens a socket connection
     *
     * @param aHost the host to connect to
     * @param aPort the port to connect to
     * @throws IOException if connection fails
     */
    public synchronized void open(String aHost, int aPort)
            throws IOException {
        host = aHost;
        port = aPort;

        /* Open a client socket connection, throws IOExceptions
       * to the calling thread.
       * RATIONALE: the caller should handle any connection-
       * related errors.
       */
        socket = new Socket(host, port);

        inReader = new BufferedReader
                (new InputStreamReader(socket.getInputStream()));
        outWriter = new PrintWriter(socket.getOutputStream(), true);
    }


    private synchronized void open() throws IOException {
        open(host, port);
    }


    /**
     * @return the SO_TIMEOUT of the Socket that this client uses. 0 returns implies that the option is disabled (i.e.,
     * timeout of infinity).
     * @throws SocketException if configuration failed
     */
    public int getSoTimeout() throws SocketException {
        if (socket != null) {
            return socket.getSoTimeout();
        } else {
            return 0;
        }
    }


    /**
     * Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. The timeout must be &gt; 0. A timeout of zero
     * is interpreted as an infinite timeout.
     *
     * @param millisecs the timeout in milliseconds
     * @throws SocketException if configuration failed
     */
    public void setSoTimeout(int millisecs) throws SocketException {
        if (socket != null) {
            socket.setSoTimeout(millisecs);
        } else {
            System.err.println("SocketCommandClient.setSoTimeout(): " +
                    "socket is null");
        }
    }


    /**
     * sends a command, retries on error which will attempt to repair a dead socket
     *
     * @param command the command
     * @return true if command was sent ok
     */

    public synchronized boolean sendCommand(String command) {
        final int maxTries = 2;

        for (int i = 0; i < maxTries; i++) {
            if (!checkOpen()) {
                continue;
            }
            outWriter.println(command);
            if (outWriter.checkError()) {
                close();
                System.err.println("IO error while sending " + command);
            } else {
                return true;
            }
        }
        return false;
    }


    /**
     * Gets a response
     *
     * @return the response or null if error
     */

    public synchronized String getResponse() {
        String response = null;

        if (!checkOpen()) {
            return null;
        }

        try {
            response = inReader.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error while reading response");
            close();
        }
        return response;
    }


    /**
     * is a response
     *
     * @return the response or null if error
     */

    public synchronized boolean isResponse() {
        boolean response = false;

        if (!checkOpen()) {
            return false;
        }

        try {
            response = inReader.ready();
        } catch (IOException ioe) {
            System.err.println("IO error while checking response");
            close();
        }
        return response;
    }


    /**
     * sends a command get a response
     *
     * @param command the command to send
     * @return the response or null if error
     */

    public synchronized String sendCommandGetResponse(String command) {
        String response = null;
        if (sendCommand(command)) {
            response = getResponse();
        }
        return response;
    }


    /** Closes the socket connection */
    public synchronized void close() {
        try {
            if (socket != null) {
                socket.close();
            } else {
                System.err.println("SocketCommandClient.close(): " +
                        "socket is null");
            }
        } catch (IOException ioe) {
            System.err.println("Trouble closing socket");
        }
        socket = null;
    }


    /** Checks to see if the socket is open, if not opens it. */
    private synchronized boolean checkOpen() {
        try {
            if (socket == null)
                open();
        }
        catch (IOException e) {
            System.err.println("SocketCommandClient.checkOpen():"
                    + "could not open socket");
            socket = null;
        }
        return socket != null;
    }

    public static void main(String[] args) {
        try {
            CommandInterpreter ci = new CommandInterpreter();
            final SocketCommandClient sci = new
                    SocketCommandClient("localhost", 7890);
            ci.add("s", new CommandInterface() {
                public String execute(CommandInterpreter ci, String[] args) {
                    StringBuilder cmd = new StringBuilder();
                    for (int i = 1; i < args.length; i++) {
                        cmd.append(args[i]).append(' ');
                    }
                    sci.sendCommand(cmd.toString());
                    return "";
                }


                public String getHelp() {
                    return "send a command";
                }
            });

            ci.add("r", new CommandInterface() {
                public String execute(CommandInterpreter ci, String[] args) {
                    while (sci.isResponse()) {
                        ci.putResponse(sci.getResponse());
                    }
                    return "";
                }


                public String getHelp() {
                    return "receive a response";
                }
            });

            ci.add("sr", new CommandInterface() {
                public String execute(CommandInterpreter ci, String[] args) {
                    StringBuilder cmd = new StringBuilder();
                    for (int i = 1; i < args.length; i++) {
                        cmd.append(args[i]).append(' ');
                    }
                    ci.putResponse(sci.sendCommandGetResponse(cmd.toString()));
                    while (sci.isResponse()) {
                        ci.putResponse(sci.getResponse());
                    }
                    return "";
                }


                public String getHelp() {
                    return "send a command, receive a response";
                }
            });

            ci.setPrompt("scc-test> ");
            ci.run();
        }
        catch (Exception e) {
            System.err.println("error occured.");
            e.printStackTrace();
            System.exit(-1);
        }
    }
}
